package com.lemonboxstudio.drawboard.widget

import android.annotation.SuppressLint
import android.content.Context
import android.graphics.Bitmap
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.Path
import android.graphics.PorterDuff
import android.graphics.PorterDuffXfermode
import android.graphics.Rect
import android.graphics.RectF
import android.graphics.Xfermode
import android.os.Handler
import android.util.AttributeSet
import android.util.Log
import android.view.MotionEvent
import android.view.SurfaceHolder
import android.view.SurfaceView
import com.cjz.jnidrawfb.DrawFrameBuffer
import com.lemonboxstudio.drawboard.Config
import com.lemonboxstudio.drawboard.data.DrawingInfo
import com.lemonboxstudio.drawboard.data.Mode
import com.lemonboxstudio.drawboard.data.PathDrawingInfo
import com.lemonboxstudio.drawboard.ktx.dp
import com.lemonboxstudio.drawboard.ktx.igNull

class DrawSurfaceView : SurfaceView, SurfaceHolder.Callback {

    private lateinit var paint: Paint
    private var path: Path? = null
    private var bufferCanvas: Canvas? = null
    private var bufferBitmap: Bitmap? = null
    private var bgBitmap: Bitmap? = null

    private var lastX: Float? = null
    private var lastY: Float? = null

    var drawPanSize = 2.dp
    var eraserSize = 4.dp

    var drawingList: ArrayList<DrawingInfo> = arrayListOf()
    var removeList: ArrayList<DrawingInfo> = arrayListOf()

    private var canEraser: Boolean = false

    private lateinit var xFerModeClear: Xfermode
    private lateinit var xFerModeDraw: Xfermode

    private var mode: Mode = Mode.DRAW

    private var callBack: DrawCallBack? = null

    private var isThreadDraw = false

    private var drawThread: Thread? = null

    private val handel: Handler = Handler()

    private val delayUiLock: Runnable = Runnable {
        uiLockStatus = UI_LOCK_STATUS_UN_LOCK
        DrawFrameBuffer.uiLock(UI_LOCK_STATUS_UN_LOCK)
    }

    private var tempLocationF = RectF()

    private var currentLineBound = RectF()
    private var currentBound = Rect()

    private var uiLockStatus = 1
    companion object {
        const val UI_LOCK_STATUS_UN_LOCK = 0
        const val UI_LOCK_STATUS_LOCK = 1
    }

    constructor(context: Context) : this(context, null)

    constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)

    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(
        context,
        attrs,
        defStyleAttr
    ) {
        init()
    }

    private fun init() {
        initBuffer()
        initPaint()
        initSurface()
    }

    private fun initSurface() {
        holder.addCallback(this)
    }

    private fun initBuffer() {
        bufferBitmap =
            Bitmap.createBitmap(Config.SCREEN_WIDTH, Config.SCREEN_HEIGHT, Bitmap.Config.ARGB_8888)
        bufferBitmap?.let { bufferCanvas = Canvas(it) }
    }

    private fun initPaint() {
        xFerModeDraw = PorterDuffXfermode(PorterDuff.Mode.SRC)
        xFerModeClear = PorterDuffXfermode(PorterDuff.Mode.CLEAR)
        paint = Paint().apply {
            this.color = Color.WHITE
            this.style = Paint.Style.STROKE
            this.strokeCap = Paint.Cap.ROUND
            this.strokeWidth = drawPanSize
            this.isAntiAlias = true
            this.xfermode = xFerModeDraw
        }
    }

    fun setOnDrawCallBack(callBack: DrawCallBack) {
        this.callBack = callBack
    }

    fun getMode(): Mode {
        return mode
    }

    fun setMode(mode: Mode) {
        if (this.mode != mode) {
            this.mode = mode
            if (mode == Mode.DRAW) {
                paint.xfermode = xFerModeDraw
                paint.strokeWidth = drawPanSize
                //paint.color = Color.WHITE
                // paint.alpha = 1
            } else {
                paint.xfermode = xFerModeClear
                //paint.color = Color.BLACK
                paint.strokeWidth = eraserSize
                // paint.alpha = 0
            }
        }
    }

    inline val canUndo get() = drawingList.size > 0
    inline val canRedo get() = removeList.size > 0

    fun undo() {
        val size = drawingList.size
        if (size > 0) {
            val info = drawingList.removeAt(size - 1)
            if (size == 1) {
                canEraser = false
            }
            removeList.add(info)
            reDraw()
            callBack?.onUndoRedoStatusChanged()
        }
    }

    fun redo() {
        val size = removeList.size
        if (size > 0) {
            val info = removeList.removeAt(size - 1)
            drawingList.add(info)
            canEraser = true
            reDraw()
            callBack?.onUndoRedoStatusChanged()
        }
    }

    fun clear() {
        if (drawingList.isNotEmpty()) {
            drawingList.clear()
        }
        if (removeList.isNotEmpty()) {
            removeList.clear()
        }
        canEraser = false

        tempLocationF.set(
            0f,
            0f,
            width.toFloat(),
            height.toFloat()
        )
        currentLineBound.union(tempLocationF)

        paint.xfermode = xFerModeClear
        bufferBitmap?.eraseColor(Color.TRANSPARENT)
        bufferBitmap?.let {
            bufferCanvas?.drawBitmap(it, 0f, 0f, paint)
        }
        drawSurface()
        paint.xfermode = xFerModeDraw
        callBack?.onUndoRedoStatusChanged()
        setLockStatus()
    }

    private fun setLockStatus() {
        if (uiLockStatus == UI_LOCK_STATUS_LOCK) {
            handel.postAtTime(delayUiLock, 230)
        }
    }

    private fun reDraw() {
        tempLocationF.set(
            0f,
            0f,
            width.toFloat(),
            height.toFloat()
        )
        currentLineBound.union(tempLocationF)
        bufferBitmap?.eraseColor(Color.TRANSPARENT)
        drawingList.map { info ->
            bufferBitmap?.let {
                bufferCanvas?.drawPath(
                    (info as PathDrawingInfo).path,
                    info.paint
                )
            }
        }
        drawSurface()
        setLockStatus()
    }

    private fun saveDrawPath() {
        if (drawingList.size == Config.MAX_CACHE_STEP) {
            drawingList.removeAt(0)
        }

        val cachePath = Path(path)
        val cachePaint = Paint(paint)
        val cacheInfo = PathDrawingInfo(cachePaint, cachePath)
        drawingList.add(cacheInfo)
        canEraser = true
        callBack?.onUndoRedoStatusChanged()
    }

    @SuppressLint("ClickableViewAccessibility")
    override fun onTouchEvent(event: MotionEvent?): Boolean {
        val x = event?.x
        val y = event?.y
        when (event?.action) {
            MotionEvent.ACTION_DOWN -> {
                uiLockStatus = UI_LOCK_STATUS_LOCK
                DrawFrameBuffer.uiLock(UI_LOCK_STATUS_LOCK)
                lastX = x
                lastY = y
                if (path == null) {
                    path = Path()
                }
                path?.moveTo(x.igNull(), y.igNull())
                currentLineBound.setEmpty()
            }

            MotionEvent.ACTION_MOVE -> {
                path?.quadTo(
                    lastX.igNull(),
                    lastY.igNull(),
                    (x.igNull() + lastX.igNull()) / 2,
                    (y.igNull() + lastY.igNull()) / 2
                )
                if (path != null) {
                    bufferCanvas?.drawPath(path!!, paint)
                }
                path?.computeBounds(tempLocationF, true)
                val padding = 5
                tempLocationF.set(
                    tempLocationF.left - padding,
                    tempLocationF.top - padding,
                    tempLocationF.right + padding,
                    tempLocationF.bottom + padding
                )
                currentLineBound.union(tempLocationF)

                bufferBitmap?.let {
                    DrawFrameBuffer.drawFBCanvas(
                        tempLocationF.left.toInt(),
                        tempLocationF.top.toInt(),
                        tempLocationF.width().toInt(),
                        tempLocationF.height().toInt(),
                        it
                    )
                }
                lastX = x
                lastY = y
            }

            MotionEvent.ACTION_CANCEL,
            MotionEvent.ACTION_UP -> {
                if (mode == Mode.DRAW || canEraser) {
                    saveDrawPath()
                }
                drawSurface()
                path?.reset()
                //handel.postAtTime(delayUiLock, 230)
            }

            else -> {}
        }
        return true
    }

    override fun surfaceCreated(holder: SurfaceHolder) {
        Log.e("tt", "surfaceCreated")
        isThreadDraw = true
        drawSurface()
    }

    override fun surfaceChanged(holder: SurfaceHolder, format: Int, width: Int, height: Int) {
        Log.e("tt", "surfaceChanged")
        isThreadDraw = true
    }

    override fun surfaceDestroyed(holder: SurfaceHolder) {
        Log.e("tt", "surfaceDestroyed")
        isThreadDraw = false
    }

    private fun drawSurface() {
        Thread {
            while (isThreadDraw) {
                currentLineBound.round(currentBound)
                val padding = 2
                currentBound[currentBound.left - padding, currentBound.top - padding, currentBound.right + padding] =
                    currentBound.bottom + padding
                val c =  holder.lockCanvas(currentBound)
                bufferBitmap?.let { c?.drawBitmap(it, currentBound, currentBound, paint) }
                holder.unlockCanvasAndPost(c)
            }
        }.start()
    }

}